home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calOutlookCSVImportExport.js < prev    next >
Encoding:
JavaScript  |  2008-03-03  |  21.0 KB  |  562 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Mozilla Calendar code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * Jussi Kukkonen <jussi.kukkonen@welho.com>.
  19.  * Portions created by the Initial Developer are Copyright (C) 2004
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Michiel van Leeuwen <mvl@exedo.nl>
  24.  *   Ernst Herbst <hb@calen.de>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. // Import
  41.  
  42. function calOutlookCSVImporter() {
  43.     this.wrappedJSObject = this;
  44. }
  45.  
  46. calOutlookCSVImporter.prototype.QueryInterface =
  47. function QueryInterface(aIID) {
  48.     if (!aIID.equals(Components.interfaces.nsISupports) &&
  49.         !aIID.equals(Components.interfaces.calIImporter)) {
  50.         throw Components.results.NS_ERROR_NO_INTERFACE;
  51.     }
  52.  
  53.     return this;
  54. };
  55.  
  56. function getOutlookCsvFileTypes(aCount) {
  57.     aCount.value = 1;
  58.     var wildmat = '*.csv';
  59.     var label = calGetString("calendar", 'filterOutlookCsv', [wildmat]);
  60.     return([{defaultExtension:'csv', 
  61.              extensionFilter: wildmat, 
  62.              description: label}]);
  63. }
  64.  
  65. calOutlookCSVImporter.prototype.getFileTypes = getOutlookCsvFileTypes;
  66.  
  67. const localeEn = {
  68.     headTitle       : "Subject",
  69.     headStartDate   : "Start Date",
  70.     headStartTime   : "Start Time",
  71.     headEndDate     : "End Date",
  72.     headEndTime     : "End Time",
  73.     headAllDayEvent : "All day event",
  74.     headAlarm       : "Reminder on/off",
  75.     headAlarmDate   : "Reminder Date",
  76.     headAlarmTime   : "Reminder Time",
  77.     headCategories  : "Categories",
  78.     headDescription : "Description",
  79.     headLocation    : "Location",
  80.     headPrivate     : "Private",
  81.  
  82.     valueTrue       : "True",
  83.     valueFalse      : "False",
  84.     
  85.     dateRe          : /^(\d+)\/(\d+)\/(\d+)$/,
  86.     dateDayIndex    : 2,
  87.     dateMonthIndex  : 1,
  88.     dateYearIndex   : 3,
  89.     dateFormat      : "%m/%d/%y",
  90.  
  91.     timeRe          : /^(\d+):(\d+):(\d+) (\w+)$/,
  92.     timeHourIndex   : 1,
  93.     timeMinuteIndex : 2,
  94.     timeSecondIndex : 3,
  95.     timeAmPmIndex   : 4,
  96.     timeAmString    : "AM",
  97.     timePmString    : "PM",
  98.     timeFormat      : "%I:%M:%S %p"
  99. };
  100.  
  101. const localeNl = {
  102.     headTitle       : "Onderwerp",
  103.     headStartDate   : "Begindatum",
  104.     headStartTime   : "Begintijd",
  105.     headEndDate     : "Einddatum",
  106.     headEndTime     : "Eindtijd",
  107.     headAllDayEvent : "Evenement, duurt hele dag",
  108.     headAlarm       : "Herinneringen aan/uit",
  109.     headAlarmDate   : "Herinneringsdatum",
  110.     headAlarmTime   : "Herinneringstijd",
  111.     headCategories  : "Categorieδn",
  112.     headDescription : "Beschrijving",
  113.     headLocation    : "Locatie",
  114.     headPrivate     : "PrivΘ",
  115.  
  116.     valueTrue       : "Waar",
  117.     valueFalse      : "Onwaar",
  118.     
  119.     dateRe          : /^(\d+)-(\d+)-(\d+)$/,
  120.     dateDayIndex    : 1,
  121.     dateMonthIndex  : 2,
  122.     dateYearIndex   : 3,
  123.     dateFormat      : "%d-%m-%y",
  124.  
  125.     timeRe          : /^(\d+):(\d+):(\d+)$/,
  126.     timeHourIndex   : 1,
  127.     timeMinuteIndex : 2,
  128.     timeSecondIndex : 3,
  129.     timeFormat      : "%H:%M:%S"
  130. };
  131.  
  132. const locales = [localeEn, localeNl];
  133.  
  134. // Windows line endings, CSV files with LF only can't be read by Outlook.
  135. const exportLineEnding = "\r\n";
  136.  
  137. /**
  138.  * Takes a text block of Outlook-exported Comma Separated Values and tries to 
  139.  * parse that into individual events.  
  140.  * 
  141.  * First line is field names, all quoted with double quotes.  Field names are
  142.  * locale dependendent.  In English the recognized field names are:
  143.  *   "Title","Start Date","Start Time","End Date","End Time","All day event",
  144.  *   "Reminder on/off","Reminder Date","Reminder Time","Categories",
  145.  *   "Description","Location","Private"
  146.  *  The fields "Title" and "Start Date" are mandatory. If "Start Time" misses
  147.  *  the event is set as all day event. If "End Date" or "End Time" miss the
  148.  *  default durations are set.
  149.  * 
  150.  * The rest of the lines are events, one event per line, with fields in the
  151.  * order descibed by the first line.   All non-empty values must be quoted.
  152.  * 
  153.  * Returns: an array of parsed calendarEvents.  
  154.  *   If the parse is cancelled, a zero length array is returned.
  155.  */ 
  156.  
  157. calOutlookCSVImporter.prototype.importFromStream =
  158. function csv_importFromStream(aStream, aCount) {
  159.     var scriptableInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
  160.                                       .createInstance(Components.interfaces.nsIScriptableInputStream);
  161.     scriptableInputStream.init(aStream);
  162.     var str = scriptableInputStream.read(-1);
  163.  
  164.     parse: { 
  165.         // parse header line of quoted comma separated column names.
  166.         var trimEndQuotesRegExp = /^"(.*)"$/m;
  167.         var trimResults = trimEndQuotesRegExp.exec( str );
  168.         var header = trimResults && trimResults[1].split(/","/);
  169.         if (header == null)
  170.             break parse;
  171.  
  172.         //strip header from string
  173.         str = str.slice(trimResults[0].length);
  174.  
  175.         var args = new Object();
  176.         //args.fieldList contains the field names from the first row of CSV
  177.         args.fieldList = header; 
  178.  
  179.         var locale;  
  180.         var i;
  181.         for (i in locales) {
  182.             locale = locales[i];
  183.             var knownIndxs = 0;
  184.             args.titleIndex = 0;
  185.             args.startDateIndex = 0;
  186.             for (var i = 1; i <= header.length; ++i) {
  187.                 switch( header[i-1] ) {
  188.                     case locale.headTitle:        args.titleIndex = i;       knownIndxs++; break;
  189.                     case locale.headStartDate:    args.startDateIndex = i;   knownIndxs++; break;
  190.                     case locale.headStartTime:    args.startTimeIndex = i;   knownIndxs++; break;
  191.                     case locale.headEndDate:      args.endDateIndex = i;     knownIndxs++; break;
  192.                     case locale.headEndTime:      args.endTimeIndex = i;     knownIndxs++; break;
  193.                     case locale.headAllDayEvent:  args.allDayIndex = i;      knownIndxs++; break;
  194.                     case locale.headAlarm:        args.alarmIndex = i;       knownIndxs++; break;
  195.                     case locale.headAlarmDate:    args.alarmDateIndex = i;   knownIndxs++; break;
  196.                     case locale.headAlarmTime:    args.alarmTimeIndex = i;   knownIndxs++; break;
  197.                     case locale.headCategories:   args.categoriesIndex = i;  knownIndxs++; break;
  198.                     case locale.headDescription:  args.descriptionIndex = i; knownIndxs++; break;
  199.                     case locale.headLocation:     args.locationIndex = i;    knownIndxs++; break;
  200.                     case locale.headPrivate:      args.privateIndex = i;     knownIndxs++; break;
  201.                 }
  202.             }
  203.             // Were both mandatory fields recognized?
  204.             if (args.titleIndex != 0 && args.startDateIndex != 0) {
  205.                 break;
  206.             }
  207.         }
  208.  
  209.         if (knownIndxs == 0 && header.length == 22) {
  210.             // set default indexes for a default Outlook2000 CSV file
  211.             args.titleIndex = 1;
  212.             args.startDateIndex = 2;
  213.             args.startTimeIndex = 3;
  214.             args.endDateIndex = 4;
  215.             args.endTimeIndex = 5;
  216.             args.allDayIndex = 6;
  217.             args.alarmIndex = 7;
  218.             args.alarmDateIndex = 8;
  219.             args.alarmTimeIndex = 9;
  220.             args.categoriesIndex = 15;
  221.             args.descriptionIndex = 16;
  222.             args.locationIndex = 17;
  223.             args.privateIndex = 20;
  224.         }  
  225.  
  226.         if (args.titleIndex == 0 || args.startDateIndex == 0) {
  227.             dump("Can't import. Life sucks\n")
  228.             break parse;
  229.         }
  230.  
  231.         // Construct event regexp according to field indexes. The regexp can
  232.         // be made stricter, if it seems this matches too loosely.
  233.         var regExpStr = "^";
  234.         for (i = 1; i <= header.length; i++) {
  235.             if (i > 1)
  236.                 regExpStr += ",";
  237.             regExpStr += "(?:\"((?:[^\"]|\"\")*)\")?"; 
  238.         }
  239.         regExpStr += "$";
  240.  
  241.         // eventRegExp: regexp for reading events (this one'll be constructed on fly)
  242.         const eventRegExp = new RegExp(regExpStr, "gm");
  243.  
  244.         // match first line
  245.         var eventFields = eventRegExp(str);
  246.  
  247.         if (eventFields == null)
  248.             break parse;
  249.  
  250.         args.boolStr = localeEn.valueTrue;
  251.         args.boolIsTrue = true; 
  252.  
  253.         var dateParseConfirmed = false;
  254.         var eventArray = new Array();
  255.         do {
  256.             // At this point eventFields contains following fields. Position
  257.             // of fields is in args.[fieldname]Index.
  258.             //    subject, start date, start time, end date, end time,
  259.             //    all day, alarm on, alarm date, alarm time,
  260.             //    Description, Categories, Location, Private
  261.             // Unused fields (could maybe be copied to Description):
  262.             //    Meeting Organizer, Required Attendees, Optional Attendees,
  263.             //    Meeting Resources, Billing Information, Mileage, Priority,
  264.             //    Sensitivity, Show time as
  265.  
  266.             var title = ("titleIndex" in args
  267.                          ? parseTextField(eventFields[args.titleIndex]) : "");
  268.             var sDate = parseDateTime(eventFields[args.startDateIndex],
  269.                                       eventFields[args.startTimeIndex],
  270.                                       locale);
  271.             var eDate = parseDateTime(eventFields[args.endDateIndex],
  272.                                       eventFields[args.endTimeIndex],
  273.                                       locale);
  274.             // Create an event only if we have a startDate. No more checks
  275.             // on sDate needed in the following process.
  276.             if (sDate) {
  277.                 var event = createEvent();
  278.  
  279.                 // Use column head in brackets if event title misses in data.
  280.                 if (title) {
  281.                     event.title = title;
  282.                 } else {
  283.                     event.title = "[" + locale.headTitle + "]";
  284.                 }
  285.  
  286.                 // Check data for all day event. Additionally sDate.isDate
  287.                 // may have been set in parseDateTime() if no time was found
  288.                 if (eventFields[args.allDayIndex] == locale.valueTrue) {
  289.                     sDate.isDate = true;
  290.                 }
  291.                 if (locale.valueTrue == eventFields[args.privateIndex])
  292.                     event.privacy = "PRIVATE";
  293.  
  294.                 if (!eDate) {
  295.                     // No endDate was found. All day events last one day and
  296.                     // timed events last the default length.
  297.                     eDate = sDate.clone();
  298.                     if (sDate.isDate) {
  299.                         // end date is exclusive, so set to next day after start.
  300.                         eDate.day += 1;
  301.                     } else {
  302.                         eDate.minute += getPrefSafe(
  303.                                         "calendar.event.defaultlength", 60);
  304.                     }
  305.                 } else {
  306.                     // An endDate was found.
  307.                     if (sDate.isDate) {
  308.                         // A time part for the startDate is missing or was
  309.                         // not recognized. We have to throw away the endDates
  310.                         // time part too for obtaining a valid event.
  311.                         eDate.isDate = true;
  312.                         // Correct the eDate if duration is less than one day.
  313.                         if (1 > eDate.subtractDate(sDate).days) {
  314.                             eDate = sDate.clone();
  315.                             eDate.day += 1;
  316.                         }
  317.                     } else {
  318.                         // We now have a timed startDate and an endDate. If the
  319.                         // end time is invalid set it to 23:59:00
  320.                         if (eDate.isDate) {
  321.                             eDate.isDate = false;
  322.                             eDate.hour   = 23;
  323.                             eDate.minute = 59;
  324.                         }
  325.                         // Correct the duration to 0 seconds if it is negative.
  326.                         if (eDate.subtractDate(sDate).isNegative ) {
  327.                             eDate = sDate.clone();
  328.                         }
  329.                     }
  330.                 }
  331.                 event.startDate = sDate;
  332.                 event.endDate = eDate;
  333.  
  334.                 // Exists an alarm true/false column?
  335.                 if ("alarmIndex" in args) {
  336.                     // Is an alarm wanted for this event?
  337.                     if (locale.valueTrue == eventFields[args.alarmIndex]) {
  338.                         var alarmDate =
  339.                                 parseDateTime(eventFields[args.alarmDateIndex],
  340.                                               eventFields[args.alarmTimeIndex],
  341.                                               locale);
  342.                         // Set to default if non valid alarmDate was achieved
  343.                         if (alarmDate) {
  344.                             event.alarmOffset = alarmDate.subtractDate(sDate);
  345.                         } else {
  346.                             var alarmOffset = Components
  347.                                               .classes["@mozilla.org/calendar/duration;1"]
  348.                                               .createInstance(Components
  349.                                               .interfaces.calIDuration);
  350.                             var units = getPrefSafe("calendar.alarms.eventalarmunit",
  351.                                                     "minutes");
  352.                             alarmOffset[units] = getPrefSafe("calendar.alarms.eventalarmlen",
  353.                                                              15);
  354.                             alarmOffset.isNegative = true;
  355.                             event.alarmOffset = alarmOffset;
  356.                         }
  357.                         event.alarmRelated = Components.interfaces.calIItemBase
  358.                                                        .ALARM_RELATED_START;
  359.                     }
  360.                 }
  361.  
  362.                 // Using the "Private" field only for getting privacy status.
  363.                 // "Sensitivity" is neglected for now.
  364.                 if ("privateIndex" in args) {
  365.                     if (locale.valueTrue == eventFields[args.privateIndex]) {
  366.                         event.privacy = "PRIVATE";
  367.                     }
  368.                 }
  369.                 
  370.                 // Avoid setting empty properties
  371.                 var txt = "";
  372.                 if ("descriptionIndex" in args) {
  373.                     txt = parseTextField(eventFields[args.descriptionIndex])
  374.                     if (txt) {
  375.                         event.setProperty("DESCRIPTION", txt);
  376.                     }
  377.                 }
  378.                 if ("categoriesIndex" in args) {
  379.                     txt = parseTextField(eventFields[args.categoriesIndex])
  380.                     if (txt) {
  381.                         event.setProperty("CATEGORIES", txt);
  382.                     }
  383.                 }
  384.                 if ("locationIndex" in args) {
  385.                     txt = parseTextField(eventFields[args.locationIndex])
  386.                     if (txt) {
  387.                         event.setProperty("LOCATION", txt);
  388.                     }
  389.                 }
  390.  
  391.                 //save the event into return array
  392.                 eventArray.push(event);
  393.             }
  394.  
  395.             //get next events fields
  396.             eventFields = eventRegExp(str);
  397.  
  398.         } while (eventRegExp.lastIndex != 0);
  399.  
  400.         // return results
  401.         aCount.value = eventArray.length;
  402.         return eventArray;
  403.  
  404.     } // end parse
  405.  
  406.     aCount.value = 0;
  407.     return new Array();
  408. };
  409.  
  410. function parseDateTime(aDate, aTime, aLocale)
  411. {
  412.     var date = Components.classes["@mozilla.org/calendar/datetime;1"]
  413.                          .createInstance(Components.interfaces.calIDateTime);
  414.  
  415.     //XXX Can we do better?
  416.     date.timezone = floating();
  417.  
  418.     var rd = aLocale.dateRe.exec(aDate);
  419.     var rt = aLocale.timeRe.exec(aTime);
  420.  
  421.     if (!rd || !rt) {
  422.         return null;
  423.     }
  424.     
  425.     date.year = rd[aLocale.dateYearIndex];
  426.     date.month = rd[aLocale.dateMonthIndex] - 1;
  427.     date.day = rd[aLocale.dateDayIndex];
  428.     if (rt) {
  429.         date.hour = Number(rt[aLocale.timeHourIndex]);
  430.         date.minute = rt[aLocale.timeMinuteIndex];
  431.         date.second = rt[aLocale.timeSecondIndex];
  432.     } else {
  433.         date.isDate = true;
  434.     }
  435.  
  436.     if (rt && aLocale.timeAmPmIndex)
  437.       if (rt[aLocale.timeAmPmIndex] != aLocale.timePmString) {
  438.         // AM
  439.         if (date.hour == 12)
  440.           date.hour = 0;
  441.       } else {
  442.         // PM
  443.          if (date.hour < 12)
  444.           date.hour += 12;
  445.     }
  446.  
  447.     dump(date+"\n");
  448.     return date;
  449. }
  450.  
  451. function parseTextField(aTextField)
  452. {
  453.   return aTextField ? aTextField.replace(/""/g, "\"") : "";
  454. }
  455.  
  456.  
  457. // Export
  458.  
  459. function calOutlookCSVExporter() {
  460.     this.wrappedJSObject = this;
  461. }
  462.  
  463. calOutlookCSVExporter.prototype.QueryInterface =
  464. function QueryInterface(aIID) {
  465.     if (!aIID.equals(Components.interfaces.nsISupports) &&
  466.         !aIID.equals(Components.interfaces.calIExporter)) {
  467.         throw Components.results.NS_ERROR_NO_INTERFACE;
  468.     }
  469.  
  470.     return this;
  471. };
  472.  
  473. calOutlookCSVExporter.prototype.getFileTypes = getOutlookCsvFileTypes;
  474.  
  475. // not prototype.export. export is reserved.
  476. calOutlookCSVExporter.prototype.exportToStream =
  477. function csv_exportToStream(aStream, aCount, aItems) {
  478.     var str = "";
  479.     var headers = [];
  480.     // Not using a loop here, since we need to be sure the order here matches
  481.     // with the orders the field data is added later on
  482.     headers.push(localeEn['headTitle']);
  483.     headers.push(localeEn['headStartDate']);
  484.     headers.push(localeEn['headStartTime']);
  485.     headers.push(localeEn['headEndDate']);
  486.     headers.push(localeEn['headEndTime']);
  487.     headers.push(localeEn['headAllDayEvent']);
  488.     headers.push(localeEn['headAlarm']);
  489.     headers.push(localeEn['headAlarmDate']);
  490.     headers.push(localeEn['headAlarmTime']);
  491.     headers.push(localeEn['headCategories']);
  492.     headers.push(localeEn['headDescription']);
  493.     headers.push(localeEn['headLocation']);
  494.     headers.push(localeEn['headPrivate']);
  495.     headers = headers.map(function(v) {
  496.         return '"' + v + '"';
  497.     });
  498.     str = headers.join(',');
  499.     str += exportLineEnding;
  500.     aStream.write(str, str.length);
  501.  
  502.     for each (item in aItems) {
  503.         if (!isEvent(item)) {
  504.             // XXX TODO: warn the user (once) that tasks are not supported
  505.             // (bug 336175)
  506.             continue;
  507.         }
  508.         var line = [];
  509.         line.push(item.title);
  510.         line.push(dateString(item.startDate));
  511.         line.push(timeString(item.startDate));
  512.         line.push(dateString(item.endDate));
  513.         line.push(timeString(item.endDate));
  514.         line.push(item.startDate.isDate ? localeEn.valueTrue : localeEn.valueFalse);
  515.         if (item.alarmOffset) {
  516.             line.push(localeEn.valueTrue);
  517.             var fireTime;
  518.             if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
  519.                 fireTime = item.startDate.clone();
  520.             } else {
  521.                 fireTime = item.endDate.clone();
  522.             }
  523.             fireTime.addDuration(item.alarmOffset);
  524.             line.push(dateString(fireTime));
  525.             line.push(timeString(fireTime));
  526.         } else {
  527.             line.push(localeEn.valueFalse);
  528.             line.push("");
  529.             line.push("");
  530.         }
  531.         line.push(txtString(item.getProperty("CATEGORIES")));
  532.         line.push(txtString(item.getProperty("DESCRIPTION")));
  533.         line.push(txtString(item.getProperty("LOCATION")));
  534.         line.push((item.privacy=="PRIVATE") ? localeEn.valueTrue : localeEn.valueFalse);
  535.  
  536.         line = line.map(function(v) {
  537.             v = String(v).replace(/"/g,'""');
  538.             return '"'+v+'"';
  539.         })
  540.         str = line.join(',') + exportLineEnding;
  541.         aStream.write(str, str.length);
  542.     }
  543.  
  544.     return;
  545. };
  546.  
  547. function dateString(aDateTime) {
  548.     return aDateTime.jsDate.toLocaleFormat(localeEn.dateFormat);
  549. }
  550.  
  551. function timeString(aDateTime) {
  552.     return aDateTime.jsDate.toLocaleFormat(localeEn.timeFormat);
  553. }
  554.  
  555. function txtString(aString) {
  556.     if (aString) {
  557.         return aString;
  558.     } else {
  559.         return "";
  560.     }
  561. }
  562.